﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Daybreak.Core.Network.Udp
{
    public class UdpNetClient : NetBase, IDisposable
    {
        private NetBase _this;

        private Socket client;

        private Queue<NetPacket> msgPool = new Queue<NetPacket>();

        private ManualResetEvent allDone = new ManualResetEvent(true);

        private double msec = 20000;

        internal int id = -1;

        internal bool connected = false;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="assemblyFile">Path to assembly where are located user's packets</param>
        public UdpNetClient(string assemblyFile)
            : base(assemblyFile)
        {
            _this = this;

            client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        }

        /// <summary>
        /// Connect to the server
        /// </summary>
        /// <param name="host">Server host name or IP</param>
        /// <param name="port">Server port</param>
        public void Connect(string host, int port)
        {
            try
            {

                IPEndPoint iep = new IPEndPoint(Dns.GetHostEntry(host).AddressList[0], port);
                EndPoint ep = (EndPoint)iep;
                client.Connect(ep);


                Thread listenThread = new Thread(new ParameterizedThreadStart(ThreadListen));
                listenThread.IsBackground = true;
                listenThread.Start(port);


                Thread sendThread = new Thread(new ThreadStart(ThreadSend));
                sendThread.IsBackground = true;
                sendThread.Start();


                SystemUdpConnectPacket pack0 = new SystemUdpConnectPacket();
                Send(pack0);

                if (ConnectedSuccessfully)
                {
                    SystemGetConfigFilePacket pack1 = new SystemGetConfigFilePacket();
                    Send(pack1);

                    StartPing();
                }
            }
            catch (SocketException ex)
            {
                NetError(ex.Message);
            }
        }

        public void Close()
        {
            StopPing();

            SystemUdpClosePacket pack0 = new SystemUdpClosePacket();
            Send(pack0);
        }

        /// <summary>
        /// Send some data
        /// </summary>
        /// <param name="packet">User's packet</param>
        public override void Send(NetPacket packet)
        {
            msgPool.Enqueue(packet);

            base.Send(packet);
        }

        protected override void PingTimer(object stateInfo)
        {
            SystemPing pack = new SystemPing();
            pack.Data = Encoding.ASCII.GetBytes(TotalMilliseconds.ToString());

            Send(pack);

            base.PingTimer(stateInfo);
        }

        private void ThreadSend()
        {
            while (true)
            {
                if (msgPool.Count != 0)
                {
                    try
                    {

                        NetPacket packet = msgPool.Dequeue();

                        byte[] buffer = new byte[2];

                        int pid = GetPacketID(packet);
                        int lpid = pid.ToString().Length;
                        buffer[0] = Convert.ToByte(lpid);

                        int lid = id.ToString().Length;
                        buffer[1] = Convert.ToByte(lid);

                        Array.Resize<byte>(ref buffer, buffer.Length + lpid + lid);

                        Array.Copy(Encoding.ASCII.GetBytes(pid.ToString()), 0, buffer, 2, lpid);

                        Array.Copy(Encoding.ASCII.GetBytes(id.ToString()), 0, buffer, 2 + lpid, lid);

                        Array.Resize<byte>(ref buffer, buffer.Length + packet.Data.Length);

                        Array.Copy(packet.Data, 0, buffer, 2 + lpid + lid, packet.Data.Length);

                        client.Send(buffer);
                    }
                    catch (SocketException ex)
                    {
                        NetError(ex.Message);
                    }
                }

                Thread.Sleep(5);
            }
        }

        private void ThreadListen(object data)
        {
            while (true)
            {
                try
                {
                    allDone.Reset();

                    IPEndPoint remoteIpEndPoint = new IPEndPoint(IPAddress.Any, (int)data);
                    EndPoint tempRemoteEP = (EndPoint)remoteIpEndPoint;

                    StateObject so = new StateObject();

                    so.socket = client;

                    client.BeginReceiveFrom(so.buffer, 0, StateObject.buffer_size, SocketFlags.None, ref tempRemoteEP, new AsyncCallback(this.AsyncEndReceiveFrom), so);

                    allDone.WaitOne();
                }
                catch (SocketException ex)
                {
                    NetError(ex.Message);
                }
            }
        }

        private void AsyncEndReceiveFrom(IAsyncResult ar)
        {
            try
            {

                StateObject so = (StateObject)ar.AsyncState;
                allDone.Set();

                Socket socket = so.socket;

                IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
                EndPoint tempRemoteEP = (EndPoint)sender;

                int size = socket.EndReceiveFrom(ar, ref tempRemoteEP);

                byte[] tmp = new byte[size];
                Array.Copy(so.buffer, 0, tmp, 0, size);

                DeterminePacket(tmp);
            }
            catch (SocketException ex)
            {
                NetError(ex.Message);
            }
        }

        /// <summary>
        /// Determine packet
        /// </summary>
        /// <param name="data">Received data</param>
        private void DeterminePacket(byte[] data)
        {
            int lid = Convert.ToInt32(data[0]);

            byte[] temp = new byte[lid];
            Array.Copy(data, 1, temp, 0, lid);

            NetPacket packet = GetPacket(int.Parse(Encoding.ASCII.GetString(temp)));

            packet.Data = new byte[data.Length - 1 - lid];
            Array.Copy(data, 1 + lid, packet.Data, 0, data.Length - 1 - lid);

            packet.ClientAction(ref _this);
        }
        //D:\projects\XnaEngine\TestNetwork\TestNetwork\bin\Release\testnetwork.exe

        public void Dispose()
        {
            client.Close();
            client.Shutdown(SocketShutdown.Both);
        }

        /// <summary>
        /// Set the time in milliseconds during which it is expected confirmation connection.
        /// </summary>
        public double ConnectionTime
        {
            set
            {
                msec = value;
            }
        }

        private bool ConnectedSuccessfully
        {
            get
            {
                double tSec = TotalMilliseconds;

                while (true)
                {
                    if (connected)
                    {
                        return connected;
                    }
                    if (TotalMilliseconds - tSec >= msec)
                    {
                        return false;
                    }

                    Thread.Sleep(50);
                }
            }
        }

        public bool Connected
        {
            get
            {
                double tSec = TotalMilliseconds;

                while (true)
                {
                    if (configLoaded)
                    {
                        return configLoaded;
                    }
                    if (TotalMilliseconds - tSec >= msec)
                    {
                        return false;
                    }

                    Thread.Sleep(50);
                }
            }
        }
    }
}
